

import 'package:flutter/widgets.dart';

abstract class Model {
  var notifier = ChangeNotifier();
  void applyChanges<T extends Model>([void callback(T model) = null]){
    (callback ??= (m){})(this);
    notifier.notifyListeners();
  }
}

class ModelProvider<T extends Model> extends StatefulWidget {
  final Widget child;
  final T model;

  const ModelProvider({Key key, @required this.model, @required this.child}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _ModelProviderState<T>();

}

class _ModelProviderState<T extends Model> extends State<ModelProvider<T>> {
  @override void dispose() {
    widget.model.notifier.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return ModelContainer<T>(
      child: widget.child,
      model: widget.model,
    );
  }
}

class ModelContainer<T extends Model> extends InheritedWidget {
  final Widget child;
  final T model;

  ModelContainer({Key key, @required this.child, @required this.model}) : super(key: key, child: child);

  @override
  bool updateShouldNotify(ModelContainer oldWidget) => true;

  static ModelContainer<T> of<T extends Model>(BuildContext context) {
    var type = _ofType<ModelContainer<T>>();
    var inheritance = context.inheritFromWidgetOfExactType(type);
    assert(inheritance != null, "Can't find any subclass of $type");
    return inheritance;
  }

  static Type _ofType<T>() => T;
}


typedef Widget WidgetBuilderWithModel<T extends Model> (BuildContext context, Widget child, T data);

class ModelDescendant<T extends Model> extends StatefulWidget {
  final WidgetBuilderWithModel<T> onUpdate;
  final T referenceModel;
  final Widget Function(BuildContext context, T data) child;

  Widget _childWidget;

  ModelDescendant({Key key, this.onUpdate, this.referenceModel, this.child}) : assert(onUpdate != null || child != null), super(key: key);
  @override
  State<StatefulWidget> createState(){

    return _ModelDescendantState<T>();
  }
}

class _ModelDescendantState<T extends Model> extends State<ModelDescendant<T>> {
  Model model;

  void _handleUpdate(){
      setState(() {});
  }

  @override dispose(){
    model.notifier.removeListener(_handleUpdate);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    model ??= (){
      var model = widget.referenceModel ?? ModelContainer.of<T>(context).model;
      model.notifier.addListener(_handleUpdate);
      return model;
    }();
    var childBuilder = widget.child ?? (context, child) => null;
    widget._childWidget ??= childBuilder(context, model);
    var onChanged = widget.onUpdate ?? (context, child, data) => child;
    var child = onChanged(context, widget._childWidget, model);
    return child;
  }
}
